package com.dennytech.tac.net;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.params.ConnRouteParams;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.util.EntityUtils;
import org.json.JSONObject;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.SystemClock;
import android.util.Log;

import com.dennytech.tac.base.Environment;
import com.dennytech.tac.model.SimpleMsg;
import com.dianping.localcache.ByteArrayCacheManager;
import com.dianping.localcache.CacheInfo;
import com.dianping.localcache.CacheManager;
import com.google.android.photostream.UserTask;

public class HttpBase {

	private static final String LOG_TAG = HttpBase.class.getSimpleName();
	//
	// CHANGE THIS WHEN RELEASE
	//
	public static boolean DEBUG = Environment.isDebug();

	public static boolean ENABLE_ERROR_DETAIL = DEBUG;

	public static final int STATUS_CODE_UNKNOWN = 0;
	public static final int STATUS_CODE_TIMEOUT = 5;
	public static final int STATUS_CODE_MALFORMED = 11;
	public static final int STATUS_CODE_CACHED = 209;

	static final SimpleMsg DEFAULT_MESSAGE = new SimpleMsg("错误", "发生未知错误", 0, 0);

	static {
		java.lang.System.setProperty("java.net.preferIPv4Stack", "true");
		java.lang.System.setProperty("java.net.preferIPv6Addresses", "false");
	}

	protected HttpRequest request;
	protected int statusCode;
	protected String statusLine;
	protected SimpleMsg message;

	public HttpRequest request() {
		return request;
	}

	public void abort() {
		HttpRequest request = this.request;
		if (request instanceof HttpRequestBase)
			((HttpRequestBase) request).abort();
		this.request = null;
	}

	public int statusCode() {
		return statusCode;
	}

	public SimpleMsg message() {
		return message == null ? DEFAULT_MESSAGE : message;
	}

	public String errorMsg() {
		if (message != null)
			return message.content();
		if (ENABLE_ERROR_DETAIL)
			return statusCode()
					+ ": "
					+ (message != null ? message.content() : DEFAULT_MESSAGE
							.content());
		else
			return DEFAULT_MESSAGE.content();
	}

	/**
	 * each api can have a difference cache type
	 * 
	 * @since 2012.10.16
	 * @author jun.deng
	 * 
	 */

	public enum CacheType {

		/**
		 * 禁用
		 */
		DISABLED,

		/**
		 * 标准缓存，由过期时间控制（一般为5分钟）
		 */
		NORMAL,

		/**
		 * 持久化缓存，命中先返回。缓存不会过期，但是超过一定时间（一般为1小时）会在后台更新缓存内容
		 */
		PERSISTENT,

		/**
		 * 关键缓存，先尝试网络请求，如果失败，则返回缓存
		 */
		CRITICAL
	}

	static boolean isCacheInited;
	@SuppressWarnings("unchecked")
	static CacheManager<byte[]> cache = CacheManager.EMPTY;
	@SuppressWarnings("unchecked")
	static CacheManager<byte[]> persistentCache = CacheManager.EMPTY;

	static class HttpCacheManager extends ByteArrayCacheManager {
		public HttpCacheManager(SQLiteDatabase db, long lifetime,
				int cacheCapacity) {
			super(db, lifetime, cacheCapacity);
		}
	}

	protected CacheType cacheType() {
		return CacheType.NORMAL;
	}

	protected long persistentAliveDuration() {
		return 3600 * 1000;
	}

	protected long persistentExpireDuration() {
		return 7 * 24 * 3600 * 1000;
	}

	public static void init(Context context) {
		if (isCacheInited)
			return;

		isCacheInited = true;

		File file = new File(context.getCacheDir(), "default_cache");
		boolean dbExists = file.exists();
		SQLiteDatabase db = null;
		try {
			db = SQLiteDatabase.openOrCreateDatabase(file, null);
		} catch (SQLiteException e) {
		}
		if (!dbExists) {
			// delete the old cache for 1.0 & 2.0
			File oldFile = new File(context.getCacheDir(), "http");
			oldFile.delete();
			db.setVersion(2);
		} else if ((db != null) && (db.getVersion() != 2)) {
			db.close();
			if (file.delete()) {
				try {
					db = SQLiteDatabase.openOrCreateDatabase(file, null);
					db.setVersion(1);
				} catch (SQLiteException e) {
				}
			} else {
				db = null;
			}
		}
		if (db != null)
			cache = new HttpCacheManager(db, 300000, 512 * 0x400); // 5min

		File pfile = new File(context.getCacheDir(), "persistent");
		boolean pdbExists = pfile.exists();
		SQLiteDatabase pdb = null;
		try {
			pdb = SQLiteDatabase.openOrCreateDatabase(pfile, null);
		} catch (SQLiteException e) {
		}
		if (!pdbExists) {
			FileFilter ff = new FileFilter() {
				@Override
				public boolean accept(File f) {
					String fn = f.getName();
					return fn.startsWith("persistent_");
				}
			};
			// delete the old cache for 1.0 & 2.0
			File[] list = context.getFilesDir().listFiles(ff);
			for (File f : list) {
				f.delete();
			}
			// delete the old cache for 3.x & 4.0
			list = context.getCacheDir().listFiles(ff);
			for (File f : list) {
				f.delete();
			}
			pdb.setVersion(3);
		} else if ((pdb != null) && (pdb.getVersion() != 3)) {
			pdb.close();
			if (pfile.delete()) {
				try {
					pdb = SQLiteDatabase.openOrCreateDatabase(pfile, null);
					pdb.setVersion(3);
				} catch (SQLiteException e) {
				}
			} else {
				pdb = null;
			}
		}
		if (pdb != null)
			persistentCache = new PersistentCacheManager(pdb);
	}

	static final ThreadLocal<Long> persistentAliveDuration = new ThreadLocal<Long>();
	static final ThreadLocal<Long> persistentExpireDuration = new ThreadLocal<Long>();

	static class PersistentCacheManager extends ByteArrayCacheManager {
		public PersistentCacheManager(SQLiteDatabase db) {
			super(db, -1, 0);
		}

		@Override
		protected CacheInfo hitCache(CacheInfo ci) {
			if (isExpired(ci))
				return null;
			Long l = persistentAliveDuration.get();
			if (l == null)
				l = lifetime();
			if (System.currentTimeMillis() - ci.create > l) {
				new FetchTask(ci.url).execute();
			}
			return ci;
		}

		@Override
		protected boolean isExpired(CacheInfo info) {
			Long l = persistentExpireDuration.get();
			if (l == null)
				return super.isExpired(info);
			long lifetime = l.longValue();
			long currentMillis = System.currentTimeMillis();
			return lifetime > 0
					&& (lifetime < currentMillis - info.create || info.create > currentMillis);
		}
	}

	static class FetchTask extends UserTask<Void, Void, Void> {
		String url;

		public FetchTask(String url) {
			this.url = url;
		}

		@Override
		public Void doInBackground(Void... params) {
			byte[] bytes = new BaseApi() {

				@Override
				protected CacheType cacheType() {
					return CacheType.DISABLED;
				}

				@Override
				public JSONObject query() {
					return null;
				}

			}.getRaw(url);
			if (bytes != null)
				persistentCache.put(url, bytes);
			return null;
		}
	}

	public String getRawStr(String url) {
		byte[] bytes = getRaw(convertURL(url));
		
		String result;
		try {
			result = new String(bytes, "utf-8");
		} catch (Exception e) {
			return null;
		}
		return result;
	}

	public byte[] getRaw(String url) {
		Log.i(LOG_TAG, "GET: " + url);

		CacheType ctype = cacheType();
		if (ctype == CacheType.NORMAL) {
			byte[] cached = cache.get(url);
			if (cached != null) {
				statusCode = STATUS_CODE_CACHED;
				return cached;
			}
		} else if (ctype == CacheType.PERSISTENT) {
			persistentAliveDuration.set(this.persistentAliveDuration());
			persistentExpireDuration.set(this.persistentExpireDuration());
			byte[] cached = persistentCache.get(url);
			persistentAliveDuration.set(null);
			persistentExpireDuration.set(null);
			if (cached != null) {
				statusCode = STATUS_CODE_CACHED;
				return cached;
			}
		}

		long startMs = SystemClock.elapsedRealtime();
		HttpGet get = new HttpGet(url);
		byte[] bytes = exec(get);
		if ((bytes == null) && (ctype == CacheType.CRITICAL)) {
			persistentAliveDuration.set(null);
			persistentExpireDuration.set(this.persistentExpireDuration());
			byte[] cached = persistentCache.get(url);
			persistentAliveDuration.set(null);
			persistentExpireDuration.set(null);
			if (cached != null) {
				statusCode = STATUS_CODE_CACHED;
				if (!disableStatistics()) {
					// sAPIContext.doStatistics(url, "cache", null, 0, null,
					// statusCode);
				}
				return cached;
			}
		}
		if (bytes == null) {
			if (!disableStatistics()) {
				// sAPIContext.doStatistics(url, sAPIContext.network(), null,
				// -1, null, statusCode);
			}
			return null;
		}
		long endMs = SystemClock.elapsedRealtime();
		if (statusCode / 100 == 2) {
			if ((ctype == CacheType.NORMAL)) {
				cache.put(url, bytes);
			} else if ((ctype == CacheType.PERSISTENT)) {
				persistentCache.put(url, bytes);
			} else if ((ctype == CacheType.CRITICAL)) {
				persistentCache.put(url, bytes);
			}
		} else {
			if ((ctype == CacheType.CRITICAL)) {
				persistentCache.remove(url);
			}
		}

		Log.i(LOG_TAG, "ELAPSE: " + (endMs - startMs) + "ms" + " | "
				+ bytes.length + "bytes");

		if (!disableStatistics()) {
			// sAPIContext.doStatistics(url, sAPIContext.network(), getIp(url),
			// endMs - startMs,
			// String.valueOf(statusCode / 100 * 100), statusCode);
		}

		return bytes;
		//
		// HttpGet get = null;
		//
		// // 转码
		// // url = convertURL(url);
		//
		// try {
		// get = new HttpGet(url);
		// } catch (Exception e) {
		// Log.w(LOG_TAG, e.getMessage());
		// }
		//
		// if (get == null) {
		// return null;
		// }
		//
		// byte[] bytes = exec(get);
		// return bytes;
	}

	protected String convertURL(String str) {
		String url = null;
		try {
			url = new String(str.trim().replace(" ", "%20")
//					.replace("&", "%26")
//					.replace(",", "%2c").replace("(", "%28")
//					.replace(")", "%29").replace("!", "%21")
//					.replace("=", "%3D").replace("<", "%3C")
//					.replace(">", "%3E").replace("#", "%23")
//					.replace("$", "%24").replace("'", "%27")
//					.replace("*", "%2A").replace("-", "%2D")
//					.replace(".", "%2E").replace("/", "%2F")
//					.replace(":", "%3A").replace(";", "%3B")
//					.replace("?", "%3F").replace("@", "%40")
//					.replace("[", "%5B").replace("\\", "%5C")
//					.replace("]", "%5D").replace("_", "%5F")
//					.replace("`", "%60").replace("{", "%7B")
//					.replace("|", "%7C").replace("}", "%7D")
					);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return url;
	}

	public byte[] postStream(String url, InputStream stream) {
		HttpPost post = new HttpPost(url);
		long length = -1;
		try {
			length = stream.available();
		} catch (IOException e) {
		}
		post.setEntity(new InputStreamEntity(stream, length));
		byte[] rbytes = exec(post);

		return rbytes;
	}

	protected byte[] exec(HttpUriRequest request) {
		HttpClient httpClient = HttpClientFactory.getHttpClient();
		byte[] buffer = null;
		this.request = request;
		try {
			HttpHost proxy = getProxy();
			ConnRouteParams.setDefaultProxy(request.getParams(), proxy);
			HttpResponse response = httpClient.execute(request);
			statusCode = response.getStatusLine().getStatusCode();
			statusLine = response.getStatusLine().getReasonPhrase();
			HttpEntity entity = response.getEntity();
			buffer = EntityUtils.toByteArray(entity);
			entity.consumeContent();
		} catch (Exception e) {
			e.printStackTrace();
			if (e instanceof SocketException
					|| e instanceof SocketTimeoutException
					|| e instanceof UnknownHostException) {
				statusCode = STATUS_CODE_TIMEOUT;
				if (!ENABLE_ERROR_DETAIL) {
					message = new SimpleMsg("网络连接", "无法连接到服务，请检查网络连接是否可用", 0, 0);
				} else {
					message = new SimpleMsg("网络连接", e.getLocalizedMessage(), 0,
							0);
				}
			} else {
				statusCode = STATUS_CODE_UNKNOWN;
				if (!ENABLE_ERROR_DETAIL) {
					message = new SimpleMsg("错误", "服务暂时不可用，请稍候再试", 0, 0);
				} else if (statusCode / 100 == 2 || statusLine == null
						|| statusLine.length() == 0) {
					message = new SimpleMsg("错误", e.getLocalizedMessage(), 0, 0);
				} else {
					message = new SimpleMsg("错误", statusLine, 0, 0);
				}
			}
		} catch (Error e) {
			Log.e(LOG_TAG, e.getLocalizedMessage());
		} finally {
			this.request = null;
		}
		return buffer;
	}

	protected HttpHost getProxy() {
		return Environment.globalProxy();
	}

	protected boolean disableStatistics() {
		return false;
	}

}
